// Copyright 2017 Baidu Inc. All Rights Reserved.
// Author: Chang Li (changli@baidu.com)
//
// Description: NTP client API implementation.

#include "baidu_ca_ntp.h"
#include "inet.h"
#include "baidu_ca_internal.h"
#include "baidu_ca_transport.h"
#include "baidu_ca_timestamp.h"
#include "baidu_ca_adapter.h"
#include "duer_log.h"

//#define NTP_DEBUG
#ifdef NTP_DEBUG
#define NTP_PRINT(fmt, ...) do {\
		printf(fmt, ##__VA_ARGS__);\
} while (0)
#else
#define NTP_PRINT(fmt, ...)
#endif

#define NTP_SERVER "s2c.time.edu.cn"	// 202.112.10.36
#define NTP_PORT (123)

#define JAN_1970 0x83aa7e80	// 2208988800 1970 - 1900 in seconds
#define LI 0
#define VN 3
#define MODE 3
#define STRATUM 0
#define POLL 4
#define PREC -6

#define NTPFRAC(x) ( 4294*(x) + ( (1981*(x))>>11 ) )
#define USEC(x) ( ( (x) >> 12 ) - 759 * ( ( ( (x) >> 10 ) + 32768 ) >> 16 ) )

typedef struct _NtpTime {
    uint32_t coarse;
    uint32_t fine;
} NtpTime;

static void ntp_get_current_time(NtpTime* time);
static void ntp_set_time(NtpTime* new_time, double adjust_time,
                         DuerTime* result_time);
static int ntp_send_packet(bca_trans_handler hdlr, const bca_addr_t* addr);
static int ntp_rfc1305_calc_set(uint32_t* data, NtpTime* arrival,
                                DuerTime* result_time);

static double ntpdiff(NtpTime* start, NtpTime* stop) {
    int a = 0;
    uint32_t b = 0;
    double offset = 0.0;
    a = stop->coarse - start->coarse;

    if (stop->fine >= start->fine) {
        b = stop->fine - start->fine;
    } else {
        b = start->fine - stop->fine;
        b = ~b;
        a -= 1;
    }

    offset = a * 1.e6 + b * (1.e6 / 4294967296.0);
    return offset;
}

void ntp_get_current_time(NtpTime* time) {
    uint32_t ts = bca_timestamp();

    time->coarse = ts / 1000;
    time->fine = NTPFRAC((ts % 1000) * 1000);
    NTP_PRINT("%s: current_time=%u, %u.\n", __FUNCTION__, time->coarse, time->fine);
}


void ntp_set_time(NtpTime* new_time, double adjust_time,
                  DuerTime* result_time) {
    result_time->sec = new_time->coarse - JAN_1970;
    result_time->usec = USEC(new_time->fine);
    result_time->usec += adjust_time;

    if (result_time->usec > 999999) {
        result_time->usec -= 1.e6;
        result_time->sec += 1;
    }
}

int ntp_rfc1305_calc_set(uint32_t* data, NtpTime* arrival,
                         DuerTime* result_time) {
    int li, vn, mode, stratum, poll, prec;
    int delay, disp, refid;
    NtpTime reftime, orgtime, rectime, xmttime;
    double el_time, st_time, adjust_time;
    int ret = -1;

    li = ntohl(((uint32_t*)data)[0]) >> 30 & 0x03;
    vn = ntohl(((uint32_t*)data)[0]) >> 27 & 0x07;
    mode = ntohl(((uint32_t*)data)[0]) >> 24 & 0x07;
    stratum = ntohl(((uint32_t*)data)[0]) >> 16 & 0xff;
    poll = ntohl(((uint32_t*)data)[0]) >> 8 & 0xff;
    prec = ntohl(((uint32_t*)data)[0]) & 0xff;

    if (prec & 0x80) {
		prec |= 0xffffff00;
	}

    delay = ntohl(((uint32_t*)data)[1]);
    disp = ntohl(((uint32_t*)data)[2]);
    refid = ntohl(((uint32_t*)data)[3]);
    reftime.coarse = ntohl(((uint32_t*)data)[4]);
    reftime.fine   = ntohl(((uint32_t*)data)[5]);
    orgtime.coarse = ntohl(((uint32_t*)data)[6]);
    orgtime.fine   = ntohl(((uint32_t*)data)[7]);
    rectime.coarse = ntohl(((uint32_t*)data)[8]);
    rectime.fine   = ntohl(((uint32_t*)data)[9]);
    xmttime.coarse = ntohl(((uint32_t*)data)[10]);
    xmttime.fine   = ntohl(((uint32_t*)data)[11]);

    NTP_PRINT("%s: li=%d, vn=%d, mode=%d, stratum=%d, poll=%d, prec=%d;\n"
              "delay=%d, disp=%d, refid=%d;\n"
              "xmttime=%u,%u\n", __FUNCTION__, li, vn, mode, stratum, poll, prec, delay, disp,
              refid, xmttime.coarse, xmttime.fine);

    el_time = ntpdiff(&orgtime, arrival);
    st_time = ntpdiff(&rectime, &xmttime);
    adjust_time = (el_time - st_time) / 2;

    int need_check = 1;
    if (need_check) {
        //TODO: faile check
        //return enum{ERROR_NUM}
    }

    ntp_set_time(&xmttime, adjust_time, result_time);

    return 0;
fail:
    return -1;
}

int ntp_send_packet(bca_trans_handler hdlr, const bca_addr_t* addr) {
    uint32_t data[12] = {0};
    NtpTime time_sent;
    int ret = -1;
    bca_size_t size = sizeof(data);

    if (size != 48) {
        DUER_LOGE("Error: size not 48");
        return -1;
    }

    memset(data, 0, sizeof(data));
    data[0] = htonl((LI << 30) | (VN << 27) | (MODE << 24)
                      | (STRATUM << 16) | (POLL << 8) | (PREC & 0xff));
    data[1] = htonl(1 << 16); // Root Delay (seconds)
    data[2] = htonl(1 << 16); // Root Dispersion (seconds)
    ntp_get_current_time(&time_sent);
    data[10] = htonl(time_sent.coarse); // Transmit Timestamp coarse
    data[11] = htonl(time_sent.fine); // Transmit Timestamp fine

    ret = bca_trans_send(hdlr, data, size, addr);

    return ret;
}

int bca_ntp_client(char* host, int timeout, DuerTime* result_time) {
    bca_trans_handler hdlr = NULL;
    bca_addr_t svr_addr;
    uint32_t incoming_data[325] = {0};
    NtpTime time_recv;
    int ret = -1;

    if (host == NULL) {
        svr_addr.host = NTP_SERVER;
        svr_addr.host_size = BCA_STRLEN(NTP_SERVER);
    } else {
        svr_addr.host = host;
        svr_addr.host_size = BCA_STRLEN(host);
    }
    svr_addr.port = NTP_PORT;
    svr_addr.type = BCA_PROTO_UDP;

    hdlr = bca_trans_acquire(NULL);
    bca_trans_connect(hdlr, &svr_addr);
    ret = ntp_send_packet(hdlr, &svr_addr);		//svr_addr is useless?
    NTP_PRINT("%s: send return=%d.\n", __FUNCTION__, ret);

    if (ret != 48) {
        DUER_LOGW("send nok, ret=%d, should resend.", ret);
        bca_trans_close(hdlr);
        return -1;
    }

    int wait_time = 0;	// ms
    if (timeout == 0) {
        timeout = 2000;
    }

    while (1) {
        ret = bca_trans_recv(hdlr, incoming_data, sizeof(incoming_data),
                             &svr_addr);	//svr_addr is useless?
        NTP_PRINT("%s: receive return=%d.\n", __FUNCTION__, ret);

        if (ret < 0) {
            DUER_LOGI("recv nok, wait for 500 ms.");
            wait_time += 500;
            if (wait_time > timeout) {
                break;
            }

			bca_sleep(500);

            continue;
        }

        ntp_get_current_time(&time_recv);
        ret = ntp_rfc1305_calc_set(incoming_data, &time_recv, result_time);
        NTP_PRINT("%s: calc_set return=%d.\n", __FUNCTION__, ret);

        if (ret == 0) {
            NTP_PRINT("NTP: success.\n");
            NTP_PRINT("%s: result time=%u, %u\n", __FUNCTION__, result_time->sec,
                      result_time->usec);
            break;
        } else {
            DUER_LOGE("%s: NTP received data, but not ok.\n", __FUNCTION__);
            break;
        }
    }

    bca_trans_close(hdlr);

    return ret;
}



